#pragma once

class BitWise
{
	public:
		enum SignState
		{
			NegativePositive,
			NegativeZeroPositive
		};

		static int GetSign(int value, SignState signState)
		{
			// NegativePositive (-1/0): true has value 1 and false has value 0
			// NegativeZeroPositive (-1/0/1): 
			return (signState == NegativePositive) ? -(value < 0) : (value > 0) - (value < 0);
		}

		static bool HasOppositeSign(int value1, int value2)
		{
			// most significant bit will be 1 for negative and 0 for positive
			// and so taking XOR will be 1 (1 ^ 0 = 1) which is negative (< 0)
			return ((value1 ^ value2) < 0);
		}

		static int Abs(int value)
		{
			using namespace std;

			// for negative value the mask will be all 1s - this is 2s complement = -1
			// for positive value it will be all 0s = 0
			const int mask = value >> ((sizeof(int)*CHAR_BIT) - 1);

			// XORing negative value with all 1s is (positive value - 1)
			// XORing positive value with all 0s is positive value
			return (value ^ mask) - mask;

			// following solution has overflow problem
			// return (value + mask) ^ mask
		}

		static bool IsPowerOfTwo(int value)
		{
			// value & (value - 1) = 0 if power of two OR >0 if not
			// !(value & (value - 1)) = 0 if positive/negative OR 1 if zero
			return value && !(value & (value - 1));
		}

		static unsigned int SetBit(unsigned int value, unsigned char bitPosition, bool reset)
		{
			// -reset is all 1s if true and all 0s if false
			// XROing with -reset gives all same bits of value if reset is false but inverted bits if reset it true
			// ANDing mask with that will set the bit but resets rest of the bits
			// XROing that with same value is actully setting or resetting the bit at the position
			return value ^= (-reset ^ value) & (0x1 << bitPosition);
		}

		// returns merged bits based on mask:
		// - bit 0 selects bit from value1
		// - bit 1 selectd bit from value2
		static unsigned int MergeBits(unsigned int value1, unsigned int value2, unsigned int mask)
		{
			// value1 XOR vaue2 = only bits that are differnet in both
			// ANDing that with mask gives bits that are different in both, plus they are set in the mask (meaning take value from value2)
			// XORing with value1 gives merged bits from both based on mask

			// Simplified (for any bit position):
			// - if value1 had bit 1 and value2 1, then (value1 ^ value2) will be 0, ANDing with mask bit 1 will be 0 and XORing with value1 will be 1
			// - if value1 had bit 0 and value2 1, then (value1 ^ value2) will be 1, ANDing with mask bit 1 will be 1 and XORing with value1 will be 1
			// - if value1 had bit 1 and value2 0, then (value1 ^ value2) will be 1, ANDing with mask bit 1 will be 1 and XORing with value1 will be 0
			// - if value1 had bit 0 and value2 0, then (value1 ^ value2) will be 0, ANDing with mask bit 1 will be 0 and XORing with value1 will be 0
			// - if value1 had bit 1 and value2 1, then (value1 ^ value2) will be 0, ANDing with mask bit 0 will be 0 and XORing with value1 will be 1
			// - if value1 had bit 0 and value2 1, then (value1 ^ value2) will be 1, ANDing with mask bit 0 will be 0 and XORing with value1 will be 0
			// - if value1 had bit 1 and value2 0, then (value1 ^ value2) will be 1, ANDing with mask bit 0 will be 0 and XORing with value1 will be 1
			// - if value1 had bit 0 and value2 0, then (value1 ^ value2) will be 0, ANDing with mask bit 0 will be 0 and XORing with value1 will be 0

			return value1 ^ ((value1 ^ value2) & mask);
		}

		static unsigned int CountBitsSet(unsigned int value)
		{
			size_t count = 0;
			while(value) value &= value - 1, ++count;
			return count;
		}

		// even bits = false
		// odd bits = true
		static unsigned int BitParity(unsigned int value)
		{
			bool parity = false;
			while(value) value &= value - 1, parity = !parity;
			return parity;
		}

		static void Swap(unsigned int &value1, unsigned int &value2)
		{
			((value1 == value2) || (value1 ^= value2, value2 ^= value1, value1 ^= value2));
		}

		static unsigned int ReverseBits(unsigned int value)
		{
			size_t size = sizeof(int) * CHAR_BIT - 1;
			unsigned int reverse = value;
			while(value) value >>= 1, reverse <<= 1, reverse |= value & 1, --size;
			reverse <<= size;
			return reverse;
		}

		static bool HasZeroByte(unsigned int value)
		{
			return ~((((value & 0x7F7F7F7F) + 0x7F7F7F7F) | value) | 0x7F7F7F7F);
		}

		static void PrintBinary(int value)
		{
			const size_t intSize = sizeof(int) * CHAR_BIT;
			char binaryStr[intSize + 1];
			unsigned int mask = 1 << (intSize-1);
			
			for(int i = 0; i < intSize; ++i)
			{
				binaryStr[i] = "01"[(value & mask) > 0];
				mask >>= 1;
			}
			binaryStr[intSize] = '\0';

			std::cout << binaryStr;
		}

		static const char* BoolToString(bool flag)
		{
			return &"False\0True\0"[flag << 2 | flag << 1];
		}

		static void UseBitWise()
		{
			using namespace std;

			cout << "Sign (positive): " << GetSign(100, NegativePositive) << endl;
			cout << "Sign (negative): " << GetSign(-100, NegativePositive) << endl;
		
			cout << "Sign (positive): " << GetSign(100, NegativeZeroPositive) << endl;
			cout << "Sign (zero): " << GetSign(0, NegativeZeroPositive) << endl;
			cout << "Sign (negative): " << GetSign(-100, NegativeZeroPositive) << endl;

			cout << "Has opposite signs: " << HasOppositeSign(1, -1) << endl;
			cout << "Doesn't have opposite signs: " << HasOppositeSign(1, 1) << endl;

			cout << "Absolute: " << Abs(-100) << endl;
			cout << "Absolute: " << Abs(100) << endl;

			cout << "!(1): " << !(1) << endl;
			cout << "!(-1): " << !(-1) << endl;
			cout << "!(0): " << !(0) << endl;

			cout << "Set bit (3rd bit in 0x100000): ";
			PrintBinary(SetBit(64 /*0x100000*/, 3, true));
			cout << endl;

			cout << "Reset bit (3rd bit in 0x10001000): ";
			PrintBinary(SetBit(136 /*0x10001000*/, 3, false));
			cout << endl;

			cout << "Merge bits (v1 0x10011001, v2 0x11001100, m 0x11110000): ";
			PrintBinary(MergeBits(153, 204, 240));
			cout << endl;

			cout << "Count bits set (v 0x10011001): " << CountBitsSet(153) << endl;
			cout << "Get bit parity (v 0x10011001): " << BoolToString(BitParity(153)*6) << endl;
			
			cout << "Reverse bits (v 0x01111011): ";
			PrintBinary(ReverseBits(123));
			cout << endl;
			
			unsigned int v1 = 123, v2 = 456;
			Swap(v1, v2);
			cout << "Swap v1=123 and v2=456): v1=" << v1 << " v2=" << v2 << endl;

			cout << "Has zero byte: v=0x77770077: " << BoolToString(HasZeroByte(0x77770077)) << endl;
			cout << "Has zero byte: v=0x77770177: " << BoolToString(HasZeroByte(0x77770177)) << endl;
		}
};

